📱 QA Mobile · Version 2.0

Subscribe Mobile · Test Scenarios
+ Playwright Mobile Automation Guide

Comprehensive mobile test documentation for ConstructQ Subscribe Mobile · 6-step wizard · 15 test areas · 78 test cases · Pixel 5 / iPhone 14 emulation · touch gestures · bottom sheet · virtual keyboard
📅 วันที่: 13 ก.ค. 2569 (2026-07-13) 👤 Owner: วุธ (PM/SA · QA Lead) 🎯 Audience: QA Mobile · Dev · Automation 📄 Source: ConstructQ_Subscribe_Mobile.html
15
Test Areas
78
Test Cases
6
Wizard Steps
2
Devices (Pixel + iPhone)

01บทนำ · About This Guide

Mobile-specific test scope · devices · gestures

เอกสารนี้เป็น Mobile Test Documentation สำหรับหน้าจอ Subscribe Mobile v2.0 ครอบคลุม:

  • Test Scenarios · 15 test areas — 6-step wizard + mobile-specific behavior
  • Test Cases · 78 cases — Positive / Negative / Boundary / UI / Integration / Mobile / A11y
  • Playwright Mobile Guide — Pixel 5 · iPhone 14 emulation · touch · geolocation · camera
  • Mobile-specific coverage — Bottom sheet modal · virtual keyboard · slip upload via camera · offline · orientation
📌
Reference file: ConstructQ_Subscribe_Mobile.html · React SPA in phone frame · 6-step wizard (steps 1-6 in UI · 0-5 index) · bilingual TH/EN · fixed portrait · bottom footer navigation · Add-User bottom sheet modal

02Mobile Overview

Layout · gestures · mobile-specific components

📱 Mobile Layout

  • Fixed viewport: Portrait 375×812 (iPhone) or 411×914 (Pixel 5) · phone frame on desktop preview
  • Topbar (sticky): Logo · TH/EN language toggle · progress dots
  • Header (per-step): Eyebrow "Step X / 6" · title · subtitle
  • Content (scrollable): Main step content · scrolls under fixed footer
  • Footer (fixed bottom): "ย้อนกลับ" + primary CTA (ถัดไป / ชำระเงิน / เริ่มใช้งาน)
  • Toast: Bottom center · above footer · auto-dismiss ~2.5s
  • Bottom sheet modal: Add User form · slide-up from bottom · backdrop

🗺️ Wizard Structure

StepTitle (TH)Title (EN)Key Elements
1ข้อกำหนดและเงื่อนไขTerms & ConditionsAccordion (11 sections) · 2 required checkboxes
2เลือกแพ็คเก็จChoose Package4 plan cards (Trial/Starter/Pro/Enterprise) · cycle toggle · Pro default
3ข้อมูลบริษัทCompany DetailsCompany · Tax ID · Address · Tambon autocomplete · Contact
4ผู้ใช้งานUsersAdd-User bottom sheet modal · phone field · role · permission matrix
5ชำระเงินPaymentDuration picker · 3 tabs (QR/Card/Bank) · slip upload · Trial notice (if Trial)
6ขอแสดงความยินดีWelcomeSuccess · order ref · pulse gold icon · CTA "เริ่มใช้งาน"

💎 4 Packages (identical to Web)

  • Trial (free) — 30-day free · locked duration · payment hidden
  • Starter — ฿3,900/mo · 10 users · 3 projects · SME
  • Professional — ฿7,900/mo · 30 users · 15 projects · default selection
  • Enterprise — Contact Sales · SSO · unlimited

📱 Mobile-Specific Components

  • Bottom sheet modal (Add User) — slides up · backdrop tap dismiss · scroll inside
  • Tambon autocomplete list — appears below input · tap to select · auto-fill 3 fields
  • T&C accordion — 11 articles · tap header to expand · smooth animation
  • Payment tabs — 3 tabs (QR default · Card · Bank) · swipe-like tap only (no gesture swipe)
  • Slip upload — camera capture or gallery pick · preview after select
  • Trial notice — replaces payment tabs when free package chosen
  • Duration picker — +/- stepper · locked state for Trial (fixed 30 days)

03Mobile Test Strategy

Devices · gestures · special mobile scenarios

📱 Target Devices

DeviceViewportDPRBrowserPriority
iPhone 14390×8443Safari (WebKit)P1
Pixel 5393×8512.75ChromeP1
iPhone SE375×6672SafariP2 (small screen)
Samsung Galaxy S9+320×6584.5ChromeP2 (narrowest)
iPad Mini768×10242SafariP3 (tablet fallback)

🎯 Test Types

TypeObjectiveCoverage
PositiveHappy path on real device size~35%
NegativeInvalid input · payment failure~20%
MobileTouch · gesture · orientation · keyboard~20%
IntegrationOmise · SlipOK · camera · autocomplete~10%
BoundaryTax ID · password · duration · viewport~8%
UILayout · safe area · font scaling~4%
A11yVoiceOver · TalkBack · contrast · touch target~3%

⚡ Priority

  • P1 — Critical · payment · signup completion · Trial lock · touch input
  • P2 — High · validation · bottom sheet · virtual keyboard behavior
  • P3 — Medium · orientation · UI polish · edge devices

🌐 Mobile-Specific Considerations

  • Touch targets — Minimum 44×44px per iOS HIG / 48×48dp per Material
  • Virtual keyboard — Fields must remain visible when keyboard opens · scroll into view
  • Camera permission — Test prompt · deny · fallback to gallery
  • Network — Test on 4G · slow 3G · offline · retry after reconnect
  • Font scaling — Respect OS accessibility font size (Dynamic Type on iOS · Font Scale on Android)
  • Safe area — Respect notch/dynamic island · home indicator

04Test Scenarios + Test Cases

15 areas · 78 detailed mobile test cases

4.1 · 🚀 App Launch + Topbar

TC IDScenario StepsExpected Result PriorityType
TC-M01เปิดหน้า Subscribe Mobile ครั้งแรก 1. Launch app · navigate /subscribe Portrait phone frame · topbar (logo · TH/EN) · Step 1 T&C · fixed footer visible P1Pos
TC-M02Progress dots on topbar 1. Advance step by step 6 dots update · current step highlighted · completed = filled P2UI
TC-M03Sticky topbar during scroll 1. Scroll content downward Topbar remains fixed at top · content scrolls under · footer stays at bottom P2Mobile
TC-M04Language toggle in topbar 1. Tap 🇹🇭 → 🇬🇧 Whole UI switches to EN · flag icon changes · state persists P1Pos
TC-M05Fixed footer buttons 1. View any step Back + primary CTA always visible at bottom · touch target ≥ 44px height P1Mobile

4.2 · 📜 Step 01 · Terms & Conditions

TC IDScenario StepsExpected Result PriorityType
TC-M06T&C accordion load · 11 articles 1. เข้า Step 01 All 11 articles collapsed by default · tap header to expand · smooth animation P2Pos
TC-M07T&C accordion · tap to expand 1. Tap article #6 header Expands smoothly · chevron rotates · content scrolls into view if needed P2Mobile
TC-M08T&C checkbox required 1. ไม่ติ๊ก T&C
2. Tap "ยอมรับและถัดไป"
Button disabled visually · or toast "กรุณายอมรับ" · block advance P1Neg
TC-M09PDPA checkbox required 1. ติ๊ก T&C · ไม่ติ๊ก PDPA
2. Tap CTA
Block advance · ต้องติ๊กทั้งคู่ P1Neg
TC-M10Both checked · advance to Step 2 1. ติ๊ก 2 กล่อง
2. Tap CTA
Advance · Step 2 loads · progress dot 2 highlighted P1Pos

4.3 · 💎 Step 02 · Choose Package

TC IDScenario StepsExpected Result PriorityType
TC-M114 plan cards stacked vertically 1. เข้า Step 02 4 cards vertical scroll · Trial + Starter + Pro + Enterprise · Pro badge "Most Popular" P1Pos
TC-M12Professional default-selected 1. เข้า Step 02 ครั้งแรก Pro card highlighted default (per Mobile v26 · task #26) P2Pos
TC-M13Cycle toggle · monthly ↔ yearly 1. Tap "รายปี" Prices update · Starter 3,900→42,900 · savings shown P1Pos
TC-M14Trial card · plain text (not chips) 1. View Trial card Quota display as plain text (per Mobile v22 · task #22) · not chip pills P2UI
TC-M15Trial subtitle 1. View Trial card Subtitle "เริ่มต้นฟรี ไม่เสียเงิน" (per Mobile v23) P3UI
TC-M16Tap card → highlight 1. Tap Starter card Card border teal + shadow · other cards dim · footer CTA enabled P1Pos
TC-M17Enterprise = Contact Sales 1. Tap Enterprise "ติดต่อทีมขาย / Contact Sales" text · no fixed price · CTA behavior "ติดต่อเรา" P1Pos
TC-M18Card touch target size 1. Measure card height Card total height ≥ 120px · easy to tap accurately P3Mobile

4.4 · 🏢 Step 03 · Company Details

TC IDScenario StepsExpected Result PriorityType
TC-M19Field sections · Company + Contact + Address 1. เข้า Step 03 3 sections with headings · stack vertically P2UI
TC-M20Tax ID input · 13-digit numeric keyboard 1. Tap Tax ID field Numeric keyboard opens (inputmode="numeric") · 13-digit limit P1Mobile
TC-M21Tax ID validation · 13 digits required 1. พิมพ์ "12345" · blur Error text under field · block save P1Neg
TC-M22Email · email keyboard 1. Tap email field Email keyboard variant (@, .com quick keys) P1Mobile
TC-M23Email format validation 1. Enter "bad-email" Error "รูปแบบอีเมลไม่ถูกต้อง" P1Neg
TC-M24Phone · tel keyboard 1. Tap phone field Telephone keypad opens (0-9 + symbols) P2Mobile
TC-M25Tambon autocomplete list 1. Type "ห้วยขวาง" in tambon Dropdown appears below input · scrollable · tap to select P1Int
TC-M26Tambon select · auto-fill 3 fields 1. Tap first suggestion District/Province/Postal auto-filled · list closes P1Int
TC-M27Tambon reordered fields (v16 spec) 1. View address section Tambon field at top · then district → province → postal (per Mobile v16 · task #16) P2UI
TC-M28Autocomplete list · dismiss on blur 1. Open list · tap outside List closes · no selection P3Mobile

4.5 · 👥 Step 04 · Users (Bottom Sheet Modal)

TC IDScenario StepsExpected Result PriorityType
TC-M29Empty state · no users 1. เข้า Step 04 Empty message + "เพิ่มผู้ใช้งาน" CTA button P2UI
TC-M30Open Add-User modal (bottom sheet) 1. Tap "เพิ่มผู้ใช้งาน" Modal slides up from bottom · backdrop dim · header + form + save/cancel P1Mobile
TC-M31Modal · form fields (5 fields) 1. Open modal name · email · phone (per Mobile v21) · password · role dropdown P1Pos
TC-M32Modal · permission matrix default collapsed 1. Open modal · view matrix Matrix collapsed default (per task #4 · reverted from #3) · tap to expand P2UI
TC-M33Modal · role select → matrix updates 1. Select role "qc_insp" Matrix shows features accessible by Inspector · view-only P2Pos
TC-M34Modal · save user 1. Fill all fields · tap "บันทึก" Modal closes · user row added to list · success toast P1Pos
TC-M35Modal · cancel 1. Open modal · tap "ยกเลิก" Modal closes · no user added · form state cleared P2Pos
TC-M36Modal · backdrop tap to dismiss 1. Open modal · tap outside sheet Modal closes · same as cancel P3Mobile
TC-M37Modal · scroll inside when content overflows 1. Open modal · expand matrix (long) Sheet content scrolls independently · backdrop stays P2Mobile
TC-M38Modal · password rules (no special-char rule) 1. Type "Password123" Accept · no special char required (per Mobile v12 · task #176) P2Bnd
TC-M39User list · delete swipe or icon 1. Tap delete icon on row Confirm dialog · delete on confirm P2Pos

4.6 · 💳 Step 05 · Payment

TC IDScenario StepsExpected Result PriorityType
TC-M40Duration picker · default 1 1. เข้า Step 05 Default 1 · +/- buttons · large touch targets P1Pos
TC-M41Duration +/- taps 1. Tap + 3 times Value 1→2→3→4 · summary recalculates each tap P1Pos
TC-M42Duration min = 1 1. Duration = 1 · Tap - - disabled state · stays at 1 P2Bnd
TC-M43Payment tabs · 3 tabs (QR default) 1. Enter Step 05 QR tab active default · Card + Bank tabs visible P1Pos
TC-M44Tab switch · tap 1. Tap Card tab Content changes to card form · active state visible P1Pos
TC-M45QR tab · display QR + amount + merchant 1. View QR tab PromptPay QR image · amount in baht · merchant info in gold box P1Pos
TC-M46Slip upload · file picker 1. Tap "อัปโหลดสลิป" Native file picker · camera or gallery options P1Mobile
TC-M47Slip upload · camera capture 1. Choose "Camera" Camera opens · capture · preview · confirm/retake P1Int
TC-M48Slip verify via SlipOK 1. Upload valid slip Loading spinner · verified < 30s · success badge · advance-enabled P1Int
TC-M49Card tab · card preview animation 1. Tap Card tab · type card number Card preview graphic shows entered digits · updates live P2UI
TC-M50Card · Omise TEST charge success 1. Enter 4242 4242 4242 4242 + name/exp/CVC Omise TEST returns success · advance to Welcome P1Int
TC-M51Card · declined 1. Enter 4000 0000 0000 0002 Error "บัตรถูกปฏิเสธ" · toast + inline · retry option P1Int
TC-M52Bank tab · display Kasikorn info 1. Tap Bank tab Bank name · branch · account no · copy button P1Pos
TC-M53Bank · copy account no 1. Tap copy icon Toast "คัดลอกแล้ว" · Clipboard API sets value P3Mobile
TC-M54Summary card · gradient background 1. Scroll to summary Teal → gold gradient · rows for package/cycle/subtotal/VAT/total P2UI

4.7 · 🎉 Step 06 · Welcome

TC IDScenario StepsExpected Result PriorityType
TC-M55Welcome screen · pulse gold icon 1. Complete payment Gold circle with icon · pulse animation 2s infinite · title "🎉" P2UI
TC-M56Welcome card · order details 1. View welcome card Package · cycle · users count · sent-to email count P1Pos
TC-M57"เริ่มใช้งาน" CTA 1. Tap CTA Redirect to /dashboard (mobile) or /login · session created P1Pos

4.8 · 🔒 Trial Lock (Mobile v22-v25)

TC IDScenario StepsExpected Result PriorityType
TC-M58Trial · payment section hidden 1. Select Trial · go Step 05 Payment tabs hidden · "trial-notice" element shows instead (per Mobile v23) P1Pos
TC-M59Trial · duration picker locked 1. Trial in Step 05 Duration = 30 days fixed · +/- disabled · locked visual (per Mobile v24 · task #24) P1Pos
TC-M60Trial · CTA "เริ่มใช้งาน" direct 1. Trial · tap CTA Skip payment validation · advance to Welcome directly · no charge P1Pos

4.9 · 👆 Touch Gestures

TC IDScenario StepsExpected Result PriorityType
TC-M61Touch target · min 44×44 px 1. Audit all buttons/links All interactive elements ≥ 44px height · Material 48dp min P2Mobile
TC-M62Tap ripple/feedback 1. Tap CTA · card · tab Visual feedback (color darken · scale) · immediate response P2Mobile
TC-M63Vertical scroll · no horizontal 1. Swipe left/right on content No horizontal scroll · body overflow-x hidden P2Mobile
TC-M64Fast scroll · footer stays fixed 1. Flick scroll top ↔ bottom Footer stays at bottom · topbar sticky · no jitter P2Mobile

4.10 · ⌨️ Virtual Keyboard

TC IDScenario StepsExpected Result PriorityType
TC-M65Keyboard opens · field scrolls into view 1. Tap bottom field · keyboard opens Field auto-scrolls above keyboard · never hidden P1Mobile
TC-M66Numeric fields · numeric keyboard 1. Tap Tax ID · phone · duration Numeric keypad opens (inputmode="numeric" or type="tel") P1Mobile
TC-M67Email · email keyboard variant 1. Tap email field Email keyboard with @ + .com shortcut P1Mobile
TC-M68Autofill · browser saved data 1. Tap company/email field · autofill available Browser autofill suggestions appear P2Mobile

4.11 · 📷 Slip Camera Upload

TC IDScenario StepsExpected Result PriorityType
TC-M69Camera permission prompt 1. First tap "Upload slip" · choose camera OS permission dialog · Allow → camera opens P1Mobile
TC-M70Camera permission denied · fallback 1. Deny permission Fallback to gallery only · toast "ต้องอนุญาตกล้อง" P2Mobile
TC-M71Slip preview after capture 1. Capture photo Preview thumbnail · file name · size · retake button P1Pos

4.12 · 🌐 Language Toggle

TC IDScenario StepsExpected Result PriorityType
TC-M72TH → EN toggle updates entire page 1. Tap EN flag All labels · steps · T&C content · package copy · buttons switch P1Pos
TC-M73Data preserved on toggle 1. Fill Step 3 · toggle EN · back TH Data intact · no reset P1Pos

4.13 · 📡 Offline / Poor Network

TC IDScenario StepsExpected Result PriorityType
TC-M74Offline mode · slip upload fails gracefully 1. Enable airplane mode · upload slip Error "ไม่มีอินเทอร์เน็ต" · retry button · no crash P2Neg
TC-M75Slow 3G · loading state visible 1. Throttle to slow 3G · submit payment Spinner visible · no double-submit · disable button while loading P2Mobile

4.14 · 🔄 Orientation + Safe Area

TC IDScenario StepsExpected Result PriorityType
TC-M76Portrait-only enforcement 1. Rotate device to landscape UI stays portrait or displays "โปรดหมุนเป็นแนวตั้ง" P3UI
TC-M77Safe area · notch/home indicator 1. iPhone 14 with notch Content respects safe-area-inset-top/bottom · no overlap P2Mobile

4.15 · ♿ Mobile Accessibility

TC IDScenario StepsExpected Result PriorityType
TC-M78VoiceOver / TalkBack · all buttons announced 1. Enable VoiceOver · swipe through page All actions announced correctly · Thai + English P2A11y
📊
Test Case Summary: รวม 78 mobile test cases · P1 = 36 · P2 = 32 · P3 = 10 · Positive 26 · Negative 8 · Boundary 3 · Integration 7 · Mobile-specific 22 · UI 10 · A11y 2

05Playwright Mobile Automation Guide

Device emulation · touch · geolocation · camera · sample tests

🚀 Mobile Setup

  1. Install: npm init playwright@latest · include Mobile Chrome + Mobile Safari projects
  2. Config: add devices Pixel 5 + iPhone 14 to playwright.config.ts
  3. Touch: enable hasTouch: true and isMobile: true in device config
  4. Geolocation/Camera: grant permissions via context.grantPermissions([...])
  5. Slip fixture: pre-generated slip JPG in fixtures/slips/ · use setInputFiles()
  6. Network throttle: use context.route() for slow 3G simulation

⚙️ playwright.config.ts · Mobile Projects

TypeScriptimport { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    locale: 'th-TH',
  },
  projects: [
    // Primary mobile targets
    { name: 'iPhone 14', use: { ...devices['iPhone 14'] }},
    { name: 'Pixel 5',   use: { ...devices['Pixel 5'] }},

    // Edge devices
    { name: 'iPhone SE',       use: { ...devices['iPhone SE'] }},
    { name: 'Galaxy S9+',      use: { ...devices['Galaxy S9+'] }},
    { name: 'iPad Mini',       use: { ...devices['iPad Mini'] }},
  ]
});

🎯 SubscribeMobilePage POM

TypeScriptimport { Page, Locator, expect } from '@playwright/test';

export class SubscribeMobilePage {
  readonly page: Page;
  readonly btnNext: Locator;
  readonly btnBack: Locator;
  readonly progressDots: Locator;

  constructor(page: Page) {
    this.page = page;
    this.btnNext = page.locator('.ph-foot button').last();
    this.btnBack = page.locator('.ph-foot button').first();
    this.progressDots = page.locator('.ph-step');
  }

  async goto() { await this.page.goto('/subscribe-mobile'); }

  async tapNext() { await this.btnNext.tap(); }

  async acceptTerms() {
    await this.page.locator('input[type="checkbox"]').first().check();
    await this.page.locator('input[type="checkbox"]').last().check();
    await this.tapNext();
  }

  async selectPackage(id: 'trial' | 'starter' | 'pro' | 'enterprise') {
    await this.page.locator(`[data-pkg="${id}"]`).tap();
    await this.tapNext();
  }

  async openAddUserSheet() {
    await this.page.getByRole('button', { name: /เพิ่มผู้ใช้งาน/ }).tap();
    await this.page.locator('.user-modal').waitFor({ state: 'visible' });
  }

  async uploadSlip(filePath: string) {
    const input = this.page.locator('input[type="file"][accept*="image"]');
    await input.setInputFiles(filePath);
  }
}

✅ Sample Test · Full Mobile E2E (Trial · no payment)

TypeScriptimport { test, expect } from '@playwright/test';
import { SubscribeMobilePage } from '../pages/SubscribeMobilePage';

test.describe('Subscribe Mobile · E2E', () => {
  test.use({ ...devices['Pixel 5'] });  // override device for this suite

  test('TC-E2E-M01 · Trial signup · skip payment', async ({ page, context }) => {
    const sub = new SubscribeMobilePage(page);
    await sub.goto();

    // Step 1 · T&C
    await sub.acceptTerms();

    // Step 2 · Trial
    await sub.selectPackage('trial');

    // Step 3 · Company (tap fields · fill · autocomplete)
    await page.getByLabel(/ชื่อบริษัท/).fill('ธาร์ณัส จำกัด');
    await page.getByLabel(/เลขผู้เสียภาษี/).fill('0105561234567');
    await page.getByLabel(/ตำบล/).fill('ห้วยขวาง');
    await page.locator('.addr-item').first().tap();  // pick autocomplete
    await page.getByLabel(/อีเมล/).fill('admin@tarnas.co.th');
    await page.getByLabel(/โทรศัพท์/).fill('0812345678');
    await sub.tapNext();

    // Step 4 · Add user via bottom sheet
    await sub.openAddUserSheet();
    await page.getByLabel(/ชื่อ/).fill('สมชาย');
    await page.getByLabel(/อีเมล/).fill('somchai@tarnas.co.th');
    await page.getByLabel(/โทรศัพท์/).fill('0812345678');
    await page.getByLabel(/รหัสผ่าน/).fill('Password123');
    await page.getByLabel(/บทบาท/).selectOption('pm');
    await page.getByRole('button', { name: /บันทึก/ }).tap();
    await sub.tapNext();

    // Step 5 · Trial notice · locked duration · direct advance
    await expect(page.locator('.trial-notice')).toBeVisible();
    await expect(page.locator('.pay-tabs')).not.toBeVisible();
    await sub.tapNext();

    // Step 6 · Welcome
    await expect(page.getByText(/ขอแสดงความยินดี|Welcome/)).toBeVisible({ timeout: 10000 });
  });

  test('TC-M59 · Trial locked duration', async ({ page }) => {
    const sub = new SubscribeMobilePage(page);
    await sub.goto();
    await sub.acceptTerms();
    await sub.selectPackage('trial');
    // jump to step 5 via progress dots
    await page.locator('[data-s="4"]').tap();
    await expect(page.locator('.dur-box.is-locked')).toBeVisible();
    await expect(page.locator('.dur-box .btn-minus')).toBeDisabled();
  });

  test('TC-M30 · Add-User modal is bottom sheet', async ({ page }) => {
    const sub = new SubscribeMobilePage(page);
    await sub.goto();
    await sub.acceptTerms();
    await sub.selectPackage('pro');
    // skip to Step 4 · Users
    await page.locator('[data-s="3"]').tap();
    await sub.openAddUserSheet();
    const sheet = page.locator('.user-modal');
    const box = await sheet.boundingBox();
    // verify sheet appears from bottom half of viewport
    expect(box!.y).toBeGreaterThan(100);
  });

  test('TC-M65 · Virtual keyboard · field scrolls into view', async ({ page }) => {
    const sub = new SubscribeMobilePage(page);
    await sub.goto();
    await sub.acceptTerms();
    await sub.selectPackage('pro');
    await page.locator('[data-s="2"]').tap();
    const phone = page.getByLabel(/โทรศัพท์/);
    await phone.focus();
    // simulate keyboard by reducing viewport height
    await page.setViewportSize({ width: 393, height: 500 });
    const box = await phone.boundingBox();
    expect(box!.y).toBeLessThan(400);  // field visible above 400px
  });
});

📷 Camera Mock (for slip upload tests)

TypeScript// Playwright doesn't emulate real camera · use setInputFiles with fixture
test('TC-M47 · Slip upload via capture (mocked)', async ({ page, context }) => {
  // Grant camera permission
  await context.grantPermissions(['camera']);

  const sub = new SubscribeMobilePage(page);
  await sub.goto();
  // ... navigate to payment step ...

  // Upload fixture slip image (bypasses native camera picker)
  await sub.uploadSlip('fixtures/slips/valid-slip.jpg');

  // Verify preview appears
  await expect(page.locator('.slip-preview img')).toBeVisible();
});

📡 Network Throttling · Slow 3G

TypeScripttest('TC-M75 · Slow 3G · loading state visible', async ({ page, context }) => {
  // Throttle · Chrome DevTools Protocol
  const client = await context.newCDPSession(page);
  await client.send('Network.emulateNetworkConditions', {
    offline: false,
    downloadThroughput: 400 * 1024 / 8,   // 400 kbps
    uploadThroughput: 400 * 1024 / 8,
    latency: 400
  });

  // ... complete signup flow · verify spinner ...
});
💡
Mobile Best Practices: Use tap() not click() for touch events · verify hasTouch in device config · use setInputFiles() for camera mocks · throttle network via CDP session · grant permissions early via context.grantPermissions() · check safe area via window.visualViewport API

06Appendix

📌 Version History

  • ConstructQ_Subscribe_Mobile.html · source · phone frame preview · 6-step wizard
  • v1.0 · earlier release (task #118)
  • v2.0 · 13 ก.ค. 2569 · Refresh · 78 mobile-specific test cases · 15 areas · added Bottom Sheet Modal · Touch · Virtual Keyboard · Camera · Offline · Orientation coverage

🔗 References

  • Playwright Mobile: https://playwright.dev/docs/emulation
  • Playwright Devices: https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
  • iOS HIG Touch Target: https://developer.apple.com/design/human-interface-guidelines/accessibility
  • Material Touch Target: https://m3.material.io/foundations/accessible-design/accessibility-basics
  • Test cards (Omise): 4242 4242 4242 4242 success, 4000 0000 0000 0002 declined

📊 Excel Companion

ดูไฟล์ ConstructQ_Subscribe_Mobile_TestScenario_v2.0.xlsx คู่กัน · 4 sheets: Summary · Test Scenarios · Test Cases · Playwright Mobile Coverage